package restful

import (
	"log"
	"moss/auth"
	"moss/conf"
	"moss/meta"
	"moss/proto"
	"moss/status"
	"net/http"
	"strings"
	"time"
	"regexp"
)

func checkAuthorization(req *http.Request) bool {
	authString := req.Header.Get("Authorization")
	// log.Println(authString)
	pattern := "AWS4-HMAC-SHA256 Credential=[a-zA-Z0-9]{20}/[[:digit:]]{8}/[a-z0-9-]*/s3/aws4_request, SignedHeaders=[a-z0-9;-]*, Signature=[[:xdigit:]]{64}"
	matched, err := regexp.MatchString(pattern, authString)
	if err != nil {
		log.Println(err)
	}
	return matched
}

func signatureCheck(req *http.Request) status.Status {
	// FIX by adding regexp
	if !checkAuthorization(req) {
		return status.InvalidArgument
	}

	accessKeyId := getAccessKeyId(req)

	accessKeySecret, ok := meta.GetAccessKeySecret(accessKeyId)
	if !ok {
		return status.InvalidAccessKeyId
	}

	remoteSignatureString := req.Header.Get(conf.HTTPHeaderAuthorization)
	auth.Sign4(req, auth.Credentials{
		AccessKeyID:     accessKeyId,
		SecretAccessKey: accessKeySecret,
		SecurityToken:   req.Header.Get("X-Amz-Security-Token")})
	localSignatureString := req.Header.Get(conf.HTTPHeaderAuthorization)

	if localSignatureString != remoteSignatureString {
		log.Println(remoteSignatureString)
		log.Println(localSignatureString)
		return status.SignatureDoesNotMatch
	}

	return status.Success
}

func isAnonymous(header http.Header) bool {
	return len(header.Get("Authorization")) == 0
}

func refererListCheck(req *http.Request) status.Status {
	bucketName := getBucketName(req)
	referer := req.Referer()
	refererList, ok := meta.GetBucketReferer(bucketName)
	if ok {
		// AllowEmptyReferer
		if len(refererList.Referer) == 0 {
			if refererList.AllowEmptyReferer == true {
				return status.Success
			} else {
				return status.AccessDenied
			}
		} else {
			allows := strings.Split(refererList.Referer, ",")
			for i := 0; i < len(allows); i++ {
				if allows[i] == referer {
					// In white list
					return status.Success
				}
			}
		}
	} else {
		// not assigned referer restriction. Pass all the way.
		return status.Success
	}

	// Not in white list
	return status.AccessDenied
}

func requestSkewCheck(req *http.Request) status.Status {
	timeStr := req.Header.Get(conf.HTTPHeaderDate)
	if len(timeStr) == 0 {
		return status.AccessDenied
	}

	timestamp, e := time.Parse("20060102T150405Z", timeStr)
	if e != nil {
		return status.AccessDenied
	}

	// FIXED
	timeSkew := time.Now().Unix()-timestamp.Unix()
	if timeSkew < 0 {
		timeSkew = -timeSkew // abs
	}

	if timeSkew > conf.Config.Limit.RequestTimeSkewLimit {
		return status.RequestTimeTooSkewed
	}

	return status.Success
}

func AuthCheck(w http.ResponseWriter, req *http.Request, allowAnonymous bool, requestId string) status.Status {
	rc := refererListCheck(req)
	if rc != status.Success {
		return rc
	}

	if allowAnonymous && isAnonymous(req.Header) {
		return status.Success
	}

	rc = requestSkewCheck(req)
	if rc != status.Success {
		return rc
	}

	rc = signatureCheck(req)
	return rc
}

func reportError(w http.ResponseWriter, req *http.Request, e status.Status, requestId string) {
	var xmlError proto.Error
	xmlError.Code = status.GetCodeString(e)
	xmlError.Message = status.GetMessage(e)
	xmlError.HostId = getHostName(req)
	xmlError.RequestId = requestId

	// special cases
	if e == status.MethodNotAllowed {
		xmlError.Method = req.Method
		if len(getObjectName(req)) == 0 {
			xmlError.ResourceType = "Bucket"
		} else {
			xmlError.ResourceType = "Object"
		}
	}

	if e == status.NotImplemented {
		xmlError.Header = req.URL.String()
	}

	sendXmlDocument(xmlError, status.GetHttpStatusCode(e), xmlError.RequestId, w, nil)
}
